/**
 * Copyright 2007 Luke Pillow
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.orsomethinglikethat.hinge.apt;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import com.sun.mirror.declaration.ClassDeclaration;
import com.sun.mirror.declaration.ConstructorDeclaration;
import com.sun.mirror.declaration.FieldDeclaration;
import com.sun.mirror.declaration.MethodDeclaration;
import com.sun.mirror.declaration.Modifier;
import com.sun.mirror.type.VoidType;

/**
 * @author lpillow
 */
public class BeanCompilerUtils
{
    /**
     * Private constructor as this is a static utility class and should not be
     * instantiated.`
     */
    private BeanCompilerUtils()
    {
        // Do Nothing
    }

    /**
     * Given a {@link ClassDeclaration}, return a collection of
     * FieldDeclarations that are considered JavaBean properties.
     * 
     * @param clazz
     * @return Collection of {@link FieldDeclaration} that represent JavaBean
     *         properties
     */
    public static Collection<FieldDeclaration> getBeanFields( ClassDeclaration clazz )
    {
        List<FieldDeclaration> beanFields = new ArrayList<FieldDeclaration>();

        for ( FieldDeclaration field : clazz.getFields() )
        {
            if ( BeanCompilerUtils.isBeanField( field, clazz ) )
            {
                beanFields.add( field );
            }
        }

        return beanFields;
    }

    /**
     * Does the given {@link ClassDeclaration} have a default public constructor
     * as required for a JavaBean
     * 
     * @param clazz
     * @return true if there is a default public constructor
     */
    public static Boolean hasDefaultPublicConstructor( ClassDeclaration clazz )
    {
        for ( ConstructorDeclaration constructor : clazz.getConstructors() )
        {
            if ( BeanCompilerUtils.isDefaultPublicConstructor( constructor ) )
            {
                return Boolean.TRUE;
            }
        }

        return Boolean.FALSE;
    }

    /**
     * @param constructor
     * @return true if the given {@link ConstructorDeclaration} is public with
     *         no parameters
     */
    protected static Boolean isDefaultPublicConstructor( ConstructorDeclaration constructor )
    {
        return constructor.getModifiers().contains( Modifier.PUBLIC ) && constructor.getParameters().size() == 0;
    }

    /**
     * @param field
     * @param clazz
     * @return true if there exists a Bean getter and setter for the given field
     *         and clazz.
     */
    public static Boolean isBeanField( FieldDeclaration field, ClassDeclaration clazz )
    {
        Boolean hasGet = Boolean.FALSE;
        Boolean hasSet = Boolean.FALSE;

        Iterator<MethodDeclaration> methods = clazz.getMethods().iterator();

        while ( methods.hasNext() && ( hasGet && hasSet ) == false )
        {
            MethodDeclaration method = methods.next();
            hasGet = !hasGet ? BeanCompilerUtils.isBeanGetter( method, field ) : hasGet;
            hasSet = !hasSet ? BeanCompilerUtils.isBeanSetter( method, field ) : hasSet;
        }

        return hasGet && hasSet;
    }

    /**
     * The getter must be public, with no parameters, and the return type must
     * match the field type.
     * 
     * @param method
     * @param field
     * @return true if the is a bean getter for the given field.
     */
    public static Boolean isBeanGetter( MethodDeclaration method, FieldDeclaration field )
    {
        Boolean result = Boolean.FALSE;

        if ( AccessorPrefix.getterFromField( field ).matchesPrefix( method, field ) )
        {
            // No parameters and same return type
            result = method.getParameters().isEmpty() && method.getReturnType().equals( field.getType() );
        }

        return result;
    }

    /**
     * The setter must be public void and accept one parameter of the same type
     * as the given field
     * 
     * @param method
     * @param field
     * @return true if the method is a bean setter for the given field
     */
    public static Boolean isBeanSetter( MethodDeclaration method, FieldDeclaration field )
    {
        Boolean result = Boolean.FALSE;

        if ( AccessorPrefix.SET.matchesPrefix( method, field ) )
        {
            // Void return type with one parameter
            if ( method.getReturnType() instanceof VoidType && method.getParameters().size() == 1 )
            {
                // Parameter is of same type as field
                result = method.getParameters().iterator().next().getType().equals( field.getType() );
            }
        }

        return result;
    }

}
